package com.tpvlog.dfs.namenode.log;

import java.util.List;

/**
 * 负责管理edits log日志的核心组件
 *
 * @author Ressmix
 */
public class FSEditlog {

    // 全局Log日志ID，递增唯一
    private volatile long txidSeq = 0L;

    // 已经同步到磁盘的最新日志的最大txid
    private volatile Long syncTxid = 0L;

    // 每个写操作日志的线程，本地都有一份日志对应的txid副本
    private ThreadLocal<Long> localTxid = new ThreadLocal<>();

    // Edits Log双缓冲区
    private DoubleBuffer doubleBuffer = new DoubleBuffer();

    // 是否有线程正在刷盘
    private volatile Boolean isSyncRunning = false;

    // 是否有一块缓冲区已满，有线程正在执行交换
    private volatile Boolean isBufferSwapping = false;

    /**
     * 记录edits log日志
     */
    public void logEdit(String content) throws Exception {
        synchronized (this) {
            // 1.如果一块缓冲区满了，有线程正在交换Buffer，则阻塞等待
            waitSwapBuffer();

            // 2.为当前日志生成全局唯一递增的txid
            long txid = ++txidSeq;
            localTxid.set(txid);

            // 3.构造一条edits log操作日志
            EditLog log = new EditLog(txid, content);

            // 4.将log写入双缓冲区
            doubleBuffer.write(log);

            // 5.判断缓冲区是否满了，满了则需要刷盘
            if (doubleBuffer.shouldSyncToDisk()) {
                // 置准备交换缓冲区的标志
                isBufferSwapping = true;
            } else {
                return;
            }
        }

        // 6.交换缓冲区并刷盘
        swapAndSync();
    }


    /**
     * 强制把内存缓冲里的数据刷入磁盘中
     */
    public void flush() {
        try {
            doubleBuffer.swapBuffer();
            doubleBuffer.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取已经刷入磁盘的edits log索引
     */
    public List<String> getFlushedTxids() {
        synchronized (this) {
            return doubleBuffer.getFlushedTxids();
        }
    }

    /**
     * 获取当前缓冲区里的数据
     */
    public String[] getBufferedEditsLog() {
        synchronized (this) {
            return doubleBuffer.getBufferedEditsLog();
        }
    }

    /*-----------------------------------------PRIVATE METHOD-----------------------------------------------------*/

    /**
     * 交换缓冲区并刷盘
     */
    private void swapAndSync() {
        // 必须要加锁，防止多个线程同时刷盘
        synchronized (this) {
            // 1.获取当前正在写的log日志的id
            long txid = localTxid.get();

            // 2.1 有线程正在刷盘
            if (isSyncRunning) {
                // 正常txid应该大于磁盘上的最新log的txid
                if (txid <= syncTxid) {
                    return;
                }

                // 等待其它线程刷盘完成
                try {
                    while (isSyncRunning) {
                        wait(1000);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            // 3.交换两块缓冲区
            doubleBuffer.swapBuffer();
            isBufferSwapping = false;

            // 4.记录最新磁盘日志id，为当前准备刷盘线程的日志id
            syncTxid = txid;
            isSyncRunning = true;

            // 5.唤醒其它等待刷盘的线程
            notifyAll();
        }

        // 6.执行刷盘
        try {
            doubleBuffer.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }

        synchronized (this) {
            // 刷盘完成后，通知其它等待刷盘完成的线程
            isSyncRunning = false;
            notifyAll();
        }
    }

    /**
     * 等待缓冲区交换完成
     */
    private void waitSwapBuffer() {
        try {
            while (isBufferSwapping) {
                wait(1000);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
